package jp.co.sega.sonic_jump.monthly.sum;

import android.content.pm.PackageManager;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.Toast;
import android.widget.EditText;
import java.io.File;
import java.io.FileOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import android.provider.MediaStore;
import android.content.ContentValues;
import android.content.ContentResolver;
import android.database.Cursor;
import android.provider.OpenableColumns;
import android.os.Environment;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.OutputStreamWriter;
import java.io.IOException;
import java.util.Collections;
import java.util.Comparator;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.OutputStream;
import java.io.InputStream;
import java.io.IOException;
import java.io.BufferedInputStream;
import java.util.Arrays;
import java.util.Map;
import java.util.HashMap;
import java.util.List;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
import android.view.ScaleGestureDetector;
import android.view.ScaleGestureDetector.SimpleOnScaleGestureListener;
import java.net.URL;
import java.net.HttpURLConnection;
import android.media.MediaPlayer;
import java.net.URLDecoder;
import java.io.UnsupportedEncodingException;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import androidx.documentfile.provider.DocumentFile;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.view.ViewGroup;
import android.widget.TableLayout;
import android.widget.TableRow;
import android.content.res.AssetFileDescriptor;
import android.content.res.AssetManager;
import android.graphics.drawable.ColorDrawable;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
import android.os.Handler;
import android.os.Looper;
import java.io.FileInputStream;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.app.Service;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.os.IBinder;
import android.os.Build;
import android.media.MediaPlayer;
import android.content.res.AssetFileDescriptor;
import java.io.IOException;
import android.text.InputType;
import android.widget.ListView;
import android.widget.AdapterView;
import android.content.Context;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.AdapterView;
import java.util.List;
import java.net.URLEncoder;
import java.net.URLDecoder;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import android.content.BroadcastReceiver;
import android.content.IntentFilter;
import android.util.TypedValue;

public class Editor {
    public static TextView label;
    public static String userlink = "";
    public static String defaulthttp = "https://raw.githubusercontent.com/GdGohan/SonicJumpMapEditor/main/";
    public static final int PICK_MAP_FILE = 1001;
    public static final int SAVE_MAP_FILE = 1002;
    public boolean bits16 = false;
    public int Off2 = 0;
    public boolean clear = true;
    public boolean hide = false;
    public CheckBox cb;
    public Button selectRoomButton;
    public Button loadButton;
    public Button saveButton;
    public Button musicButton;
    public Button resetButton;
    public Button bkButton;
    public Button plButton;
    public Button configButton;
    public Button ambientButton;
    public String[] rr;
    public boolean isDragging = false;
    
    public class MapObject {
    public int tipo;
    public int x;
    public int y;
    public int roomId; // <- novo campo

    public MapObject(int tipo, int x, int y, int roomId) {
        this.tipo = tipo;
        this.x = x;
        this.y = y;
        this.roomId = roomId;
    }

    // Construtor alternativo se precisar
    public MapObject(int tipo, int x, int y) {
        this(tipo, x, y, -1); // -1 indica indefinido
    }
}

    public List<MapObject> objects = new ArrayList<>();
    public int tileSize = 32;
    public Paint paint = new Paint();
    public byte[] realmap;
    public byte[] realmap2;
    public Uri currentFileUri;
    public View mapView;
    public static final int REQUEST_CODE_OPEN_DOCUMENT_TREE = 1003;
    public int currentmap = 0;
    public int[] roomsizecopy = {288, 324, 366, 326, 342, 382, 334, 398, 398, 404, 364, 458, 492, 464, 452, 444, 460, 480, 420, 588, 674};
    public int[] roomsize = new int[21];
    public int[] roomindex = new int[21];
    public int[] roomsize2 = new int[21];
    public int[] roomindex2 = new int[21];
    public float offsetX = 0;
    public float offsetY = 0;
    public float lastTouchX;
    public float lastTouchY;
    public String[] rooms = new String[21];
    public String[] rooms2005 = new String[6];
    public InputStream stageData = null;
    public InputStream stage = null;
    public boolean s2005 = false;
    public int maxTipo = -1;
    public byte[] stageDataCopy = null;
    public MapObject selectedObject = null;
    public volatile ArrayList<ArrayList<String>> mp3Links = null;
    //public MediaPlayer mediaPlayer = null;
    public volatile ArrayList<ArrayList<String>> nomes = null;
    public boolean isScaling = false;
    public boolean isScaling2 = false;
    public SharedPreferences prefs = null;
    public String treeUriString = "";
    public String[] musics = {"https://archive.org/download/satbk-ost/","https://archive.org/download/sonic-rush-original-soundtrack-flac-scans/Sonic%20Rush%20Original%20Soundtrack/SONIC%20RUSH%20Original%20Groove%20Rush/","http://web.archive.org/web/20190531003841/https://projects.sonicstadium.org/music-album-2012/","https://archive.org/download/sonic-mana-original-soundtrack/","https://archive.org/download/5-21-white-space-medley/Sonic%20Generations%20Original%20Soundtrack/Sonic%20Generations%20~White%20Space-Time%20-%20Blue%20Adventure~%20Complete%20Video%20Game%20Soundtrack%20%5BFLAC%5D/","https://archive.org/download/egg-dragoon/Disc%201/","https://archive.org/download/egg-dragoon/Disc%202/","https://archive.org/download/egg-dragoon/Disc%203/","https://archive.org/download/sonicheroesoriginalsoundtrack20thanniversaryedition/Sonic%20Heroes%20Original%20Soundtrack%2020th%20Anniversary%20Edition/","https://archive.org/download/sonic-riders-original-soundtrack-speedbeats-grand-prix/", "https://archive.org/download/2-03-score-dark-beginnings/-%20Perfect%20Reflections%20Shadow%20the%20Hedgehog%20Original%20Soundtrack/","https://archive.org/download/SonictheHedgehogGEN/","https://archive.org/download/SonicTheHedgehog2/","https://archive.org/download/sonic-cd-complete-ost_202207/","https://archive.org/download/sonic3_knuckles_ost/","https://archive.org/download/sonic-3d-blast-saturn-flac-16bap/Sonic%203D%20%28Saturn%29/","https://archive.org/download/sonic_adventure_music_experience/","https://archive.org/download/sonic_adventure_music_experience_vol_2/","https://archive.org/download/05-spiral-madness/"};
    public String[] musicsZip = null; //{"https://www.soahcity.com/musicfiles/2008_sonic_riders2/"};
    public String[] musicsalbum = {"SATBK OST","Sonic Rush OST","TSSMA 2012","Sonic Mania OST","Sonic Generations OST","Sonic Unleashed DISC1","Sonic Unleashed DISC2","Sonic Unleashed DISC3","Sonic Heroes OST","Sonic Riders OST","Shadow Generations OST","Sonic 1 OST","Sonic 2 OST","Sonic CD OST","Sonic 3&K OST","Sonic 3D Blast OST (Saturn)","Sonic Adventure Music Experience vol.1","Sonic Adventure Music Experience vol.2","Sonic Riders Zero Gravity OST"};
    public ArrayList<String> nomesGrupos = null;
    public int[] life = new int[21];
    public String[] stagename = new String[21];
    public String[] BlockPics = new String[21];
    public String[] EdgePics = new String[21];
    public String[] ExtrasPic = new String[21];
    public String[] StripPics = new String[21];
    public String[] TilesPics = new String[21];
    public String[] currentmusic = new String[21];
    public int[] actNumber = new int[21];
    public boolean custommusic = false;
    public LinearLayout mainLayout;
    public int[] cores = {
    Color.WHITE,
    Color.rgb(173, 216, 230), // azul claro
    Color.rgb(0, 0, 139),     // azul escuro
    Color.rgb(144, 238, 144), // verde claro
    Color.rgb(0, 100, 0),     // verde escuro
    Color.rgb(255, 182, 193), // rosa claro
    Color.rgb(199, 21, 133),  // rosa escuro
    Color.BLACK,
    };
    public int[] indiceCor = {0};
    public String amusic = "";
    public int bcolor = Color.BLACK;
    public Button musicpsButton;
    public final BroadcastReceiver playerStateReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        boolean isPlaying = intent.getBooleanExtra("isPlaying", false);
        musicButtonText(isPlaying);
    }
};

    public void musicButtonText(boolean isPlaying) {
        if (!isPlaying) {
            if (musicpsButton != null) musicpsButton.setText("\u266B\u0020\u2023"); // ♫ ‣
        } else {
            if (musicpsButton != null) musicpsButton.setText("\u266B\u0020\uD80C\uDE95"); // ♫ 𓊕
        }
    }
    
    public String getFileNameFromUri(Uri uri) {
    String result = null;
    if (uri.getScheme().equals("content")) {
        try (Cursor cursor = Shell.sInstance.getContentResolver().query(uri, null, null, null, null)) {
            if (cursor != null && cursor.moveToFirst()) {
                int nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
                if (nameIndex >= 0) {
                    result = cursor.getString(nameIndex);
                }
            }
        }
    }
    if (result == null) {
        result = uri.getPath();
        int cut = result.lastIndexOf('/');
        if (cut != -1) result = result.substring(cut + 1);
    }
    return result;
}

    public void EditorMusicZip(final String[] m, final String s, final boolean z) {
    final Handler handler = new Handler(Looper.getMainLooper());
new Thread(new Runnable() {
    @Override
    public void run() {
        try {
        boolean hasNumber = z;
        String extension = s;
        String[] musics = m;
        int i = 0;
        while (true) {
            ArrayList<String> grupos = new ArrayList<>();
            URL url = new URL(musics[i]);
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            conn.setRequestMethod("GET");
            conn.setConnectTimeout(10000);
            conn.setReadTimeout(10000);

            BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
            String line;
            while ((line = reader.readLine()) != null) {
                // Busca links .mp3 com regex simples
                // Exemplo: <a href="nome-do-arquivo.mp3">
                if (line.contains(extension)) {
                    /*int hrefIndex = line.indexOf("href=\"");
                    while (hrefIndex != -1) {*/
                    Pattern p = Pattern.compile("href\\s*=\\s*\"([^\"]+\\" + extension + ")\"", Pattern.CASE_INSENSITIVE);
                    Matcher m = p.matcher(line);
                    while (m.find()) {
                        String link = m.group(1);
                        /*int start = hrefIndex + 6;
                        int end = line.indexOf("\"", start);
                        if (end == -1) break;
                        String link = line.substring(start, end);*/
                        if (link.endsWith(extension)) {
                            // Monta URL absoluta se for relativa
                            if (!link.startsWith("http")) {
                                //link = musics[i] + link;
                            URL absolute = new URL(url, link);
                            link = absolute.toString();
                            }
                            grupos.add(link);
                        }
                    }
                }
            }
            if (!grupos.isEmpty()) {
            mp3Links.add(grupos);
            }
            reader.close();
            conn.disconnect();
            if (i == musics.length -1 && musicsZip != null && musics != musicsZip) {
            i = 0;
            extension = ".zip";
            musics = musicsZip;
            } else {
            if (i == musics.length -1 && (musicsZip == null || musics == musicsZip)) break;
            i++;
            }
            }

for (ArrayList<String> grupo : mp3Links) {
    ArrayList<String> nomess = new ArrayList<>();
    for (String url2 : grupo) {
        int lastSlash = url2.lastIndexOf('/');
        if (lastSlash != -1 && lastSlash < url2.length() - 1) {
            String nomeArquivo = url2.substring(lastSlash + 1);
            try {
                nomeArquivo = URLDecoder.decode(nomeArquivo, "UTF-8");
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
            hasNumber = nomeArquivo.contains(".zip") ? true : false;
            if (hasNumber && nomeArquivo.matches(".*\\d.*") || !hasNumber) nomess.add(nomeArquivo.replace((hasNumber ? ".zip" : extension), ""));
        }
    }
    nomes.add(nomess);
}
        } catch (Exception e) {
            e.printStackTrace();
            final Exception e2 = e;
            handler.post(new Runnable() {
                @Override
                public void run() {
                    Toast.makeText(Shell.sInstance, "Error: " + e2.getMessage(), Toast.LENGTH_LONG).show();
                }
            });
        }
        }
}).start();
    }

    public Editor () {
        Shell.sInstance.registerReceiver(playerStateReceiver, new IntentFilter("PLAYER_STATE_CHANGED"));
        cb = new CheckBox(Shell.sInstance);
        mp3Links = new ArrayList<ArrayList<String>>();
        //mediaPlayer = new MediaPlayer();
        nomes = new ArrayList<>();
        nomesGrupos = new ArrayList<>();
        EditorMusicZip(musics, ".mp3", false);
        for (int j = 0; j < musicsalbum.length; j++) {
        nomesGrupos.add(musicsalbum[j]);
        }
                int index = 0;
                for (int j = 1; j <= 7; j++) {
                for (int i = 1; i <= 3; i++) {
                rooms[index] = "Room " + j + "-" + i;
                index++;
                }
                }
                for (int j = 0; j < 6; j++) {
                rooms2005[j] = "Room " + (j+1);
                }

        roomsize = roomsizecopy;
        int off = 0;
        for (int x = 0; x < rooms.length; x++) {
            roomindex[x] = off;
            off = (int) (roomsize[x] + off);
        }
        
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
        if (Shell.sInstance.checkSelfPermission(android.Manifest.permission.WRITE_EXTERNAL_STORAGE) 
                != PackageManager.PERMISSION_GRANTED) {
            Shell.sInstance.requestPermissions(new String[]{android.Manifest.permission.WRITE_EXTERNAL_STORAGE}, 
                               200);
        } else {
            //selectMapFile();
            loadMapFile2(null);
        }
    } else {
        //selectMapFile();
        loadMapFile2(null);
    }
    }

    public void selectMapFile() {
        Intent intent;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { // API 19+
            intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
        } else {
            intent = new Intent(Intent.ACTION_GET_CONTENT);
        }
        intent.setType("*/*");
        intent.addCategory(Intent.CATEGORY_OPENABLE);
        Shell.sInstance.startActivityForResult(intent, PICK_MAP_FILE);
    }

    /*public void saveMapFile() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { // API 19+
            Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
            intent.setType("application/octet-stream");
            intent.putExtra(Intent.EXTRA_TITLE, "new_map.bin");
            startActivityForResult(intent, SAVE_MAP_FILE);
        } else {
            saveMapFileLegacy();
        }
    }*/
    public void saveMapFile() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
    prefs = PreferenceManager.getDefaultSharedPreferences(Shell.sInstance);
    treeUriString = prefs.getString("tree_uri", null);
if (treeUriString != null) {
    Uri treeUri = Uri.parse(treeUriString);
    DocumentFile pickedDir = DocumentFile.fromTreeUri(Shell.sInstance, treeUri);

    DocumentFile mySubfolder = pickedDir.findFile("SonicJump");
    if (mySubfolder == null) {
        mySubfolder = pickedDir.createDirectory("SonicJump");
    }
    
    final DocumentFile finalf = mySubfolder;

    String[] formats = {"Save as .bin", "Save as .txt"};
    AlertDialog.Builder builder = new AlertDialog.Builder(Shell.sInstance);
    builder.setTitle("Choose the save format\nIt will be saved in SonicJump subfolder");

    builder.setItems(formats, new DialogInterface.OnClickListener() {
    @Override
    public void onClick(DialogInterface dialog, int which) {
        boolean asText = (which == 1);
        String fileName = s2005 ? getFileNameFromUri(currentFileUri) + (asText ? ".txt" : ".bin") : (asText ? "decoded_map.txt" : "map.bin");
        DocumentFile newFile = finalf.createFile(asText ? "text/plain" : "application/octet-stream", fileName);
        saveMapFile2(newFile.getUri(), asText);
    }
});

    builder.setNegativeButton("Cancel", null);
    builder.show();
} else {
    // Se não tiver URI salvo, peça usando ACTION_OPEN_DOCUMENT_TREE uma vez
    Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
    Shell.sInstance.startActivityForResult(intent, REQUEST_CODE_OPEN_DOCUMENT_TREE);
}
        /*final String[] mimeTypes = {
            "application/octet-stream", // .bin
            "text/plain"                // .txt
        };
        Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);*/
        //intent.setType("*/*");
        /*intent.putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes);
        intent.putExtra(Intent.EXTRA_TITLE, s2005 ? getFileNameFromUri(currentFileUri) + ".txt" : "decoded_map.txt"); // Default
        startActivityForResult(intent, SAVE_MAP_FILE);*/
    } else {
        showSaveFormatDialog();
    }
}
    public void showSaveFormatDialog() {
    String[] formats = {"Save as .bin", "Save as .txt"};

    AlertDialog.Builder builder = new AlertDialog.Builder(Shell.sInstance);
    builder.setTitle("Choose the save format\nIt will be saved in Documents/SonicJump");

    builder.setItems(formats, new DialogInterface.OnClickListener() {
    @Override
    public void onClick(DialogInterface dialog, int which) {
        boolean asText = (which == 1);
        saveMapFileLegacy(asText);
    }
});

    builder.setNegativeButton("Cancel", null);
    builder.show();
}

    public void saveMapFileLegacy(boolean asText) {
    //updateRealMapFromObjects(currentmap);
    buildRealMap2AllRooms();

    if (!android.os.Environment.getExternalStorageState().equals(android.os.Environment.MEDIA_MOUNTED)) {
        final Handler handler = new Handler(Looper.getMainLooper());
            handler.post(new Runnable() {
                @Override
                public void run() {
        Toast.makeText(Shell.sInstance, "External storage unavailable", Toast.LENGTH_SHORT).show();
        }
        });
        return;
    }

    File dir = new File(android.os.Environment.getExternalStorageDirectory(), "Documents/SonicJump");
    if (!dir.exists()) {
        if (!dir.mkdirs()) {
        final Handler handler = new Handler(Looper.getMainLooper());
            handler.post(new Runnable() {
                @Override
                public void run() {
            Toast.makeText(Shell.sInstance, "Failed to create external directory", Toast.LENGTH_SHORT).show();
            }
            });
            return;
        }
    }

    File file = new File(dir, asText ? "decoded_map.txt" : "new_map.bin");

    try {
        if (asText) {
            // Salvar como TXT
            try (BufferedWriter writer = new BufferedWriter(new FileWriter(file))) {
                for (int room = 0; room < rr.length; room++) {
                    StringBuilder sb = new StringBuilder();

              sb.append((!rr[room].equals("Room 1-1") ? "\n" : ""))
              .append(rr[room])
              .append("\nRoomSize=").append(roomsize2[room]).append("\n");

              if (life[room] != 0 &&
                  BlockPics[room] != null && !BlockPics[room].isEmpty() &&
                  EdgePics[room] != null && !EdgePics[room].isEmpty() &&
                  ExtrasPic[room] != null && !ExtrasPic[room].isEmpty() &&
                  StripPics[room] != null && !StripPics[room].isEmpty() &&
                  TilesPics[room] != null && !TilesPics[room].isEmpty()) {
                   sb.append("Life Count=").append(life[room]).append("\n")
                  .append("BlockPics=").append(BlockPics[room]).append("\n")
                  .append("EdgePics=").append(EdgePics[room]).append("\n")
                  .append("ExtrasPic=").append(ExtrasPic[room]).append("\n")
                  .append("StripPics=").append(StripPics[room]).append("\n")
                  .append("TilesPics=").append(TilesPics[room]).append("\n")
                  .append("StageName=").append(stagename[room]).append("\n")
                  .append("ActNumber=").append(actNumber[room]).append("\n");
              }

              if (custommusic && currentmusic[room] != null && !currentmusic[room].isEmpty()) {
              sb.append("Music=").append(URLEncoder.encode(currentmusic[room], "utf-8")).append("\n");
              }
            int offset = roomindex2[room];
            int size = roomsize2[room];
            for (int i = 0; i < size; i += 3) {
                int v = realmap2[offset + i];
                int yHigh = realmap2[offset + i + 1] & 0xFF;
                int yLow = realmap2[offset + i + 2] & 0xFF;
                int y = (yHigh << 8) | yLow;
                int v2 = v + 128;
                int type = v2 >> 4;
                int x = v2 & 15;
                sb.append("Type=").append(type)
                  .append(", X=").append(x)
                  .append(", Y=").append(y).append("\n");
            }
                writer.write(sb.toString());
                }
            }
        currentFileUri = Uri.fromFile(file);
        roomindex = new int[21];
        loadTxtToRealmap(currentFileUri);
        } else {
            // Salvar como BIN
            OutputStream os = new FileOutputStream(file);
DataOutputStream dos = new DataOutputStream(os);
dos.writeInt(rr.length);
for (int size : roomsize2) {
    dos.writeInt(size);
}
dos.write(realmap2);

for (int i = 0; i < rr.length; i++) {
        dos.writeInt(life[i] != 0 ? life[i] : 2);
        dos.writeUTF(BlockPics[i] != null && !BlockPics[i].isEmpty() ? BlockPics[i] : "");
        dos.writeUTF(EdgePics[i] != null && !EdgePics[i].isEmpty() ? EdgePics[i] : "");
        dos.writeUTF(ExtrasPic[i] != null && !ExtrasPic[i].isEmpty() ? ExtrasPic[i] : "");
        dos.writeUTF(StripPics[i] != null && !StripPics[i].isEmpty() ? StripPics[i] : "");
        dos.writeUTF(TilesPics[i] != null && !TilesPics[i].isEmpty() ? TilesPics[i] : "");
        dos.writeUTF(stagename[i] != null && !stagename[i].isEmpty() ? stagename[i] : "");
        dos.writeInt(actNumber[i]);
        dos.writeUTF(custommusic && currentmusic[i] != null && !currentmusic[i].isEmpty() ? currentmusic[i] : "");
        }

dos.close();
        }
        
        final File f = file;
        final Handler handler = new Handler(Looper.getMainLooper());
            handler.post(new Runnable() {
                @Override
                public void run() {
        Toast.makeText(Shell.sInstance, "File saved: " + f.getAbsolutePath(), Toast.LENGTH_LONG).show();
        }
        });
        currentFileUri = Uri.fromFile(file);
        loadMapFile2(currentFileUri);

    } catch (IOException e) {
        e.printStackTrace();
        final Handler handler = new Handler(Looper.getMainLooper());
            handler.post(new Runnable() {
                @Override
                public void run() {
        Toast.makeText(Shell.sInstance, "Error saving file", Toast.LENGTH_SHORT).show();
        }
        });
    }
}

    /*public void saveMapFileLegacy() {
    updateRealMapFromObjects(currentmap);
    if (!android.os.Environment.getExternalStorageState().equals(android.os.Environment.MEDIA_MOUNTED)) {
        Toast.makeText(Shell.sInstance, "External storage unavailable", Toast.LENGTH_SHORT).show();
        return;
    }
    File dir = new File(android.os.Environment.getExternalStorageDirectory(), "Documents");
    if (!dir.exists()) {
        if (!dir.mkdirs()) {
            Toast.makeText(Shell.sInstance, "Failed to create external directory", Toast.LENGTH_SHORT).show();
            return;
        }
    }
    File file = new File(dir, "new_map.bin");
    OutputStream os = null;
    try {
        os = new FileOutputStream(file);
        os.write(realmap);
        Toast.makeText(Shell.sInstance, "File saved in: " + file.getAbsolutePath(), Toast.LENGTH_LONG).show();
    } catch (IOException e) {
        e.printStackTrace();
        Toast.makeText(Shell.sInstance, "Error saving external file", Toast.LENGTH_SHORT).show();
    } finally {
        if (os != null) {
            try {
                os.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}*/

    public byte[] readFully(InputStream input) throws IOException {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        byte[] buffer = new byte[4096];
        int bytesRead;
        while ((bytesRead = input.read(buffer)) != -1) {
            baos.write(buffer, 0, bytesRead);
        }
        return baos.toByteArray();
    }

    public void loadMapFile2(Uri uri) {
    rr = s2005 ? rooms2005 : rooms;
    if (uri == null) {
        try {
        InputStream in = Shell.sInstance.getAssets().open("map.bin");
        if (in == null) {
        final Handler handler = new Handler(Looper.getMainLooper());
            handler.post(new Runnable() {
                @Override
                public void run() {
           Toast.makeText(Shell.sInstance, "Could not open file", Toast.LENGTH_SHORT).show();
           }
           });
           return;
        }
        realmap = readFully(in);
        bits16 = false;
        final Handler handler = new Handler(Looper.getMainLooper());
            handler.post(new Runnable() {
                @Override
                public void run() {
        Toast.makeText(Shell.sInstance, "8bits", Toast.LENGTH_SHORT).show();
        }
        });
        loadRoomOriginal(currentmap);
        Shell.sInstance.runOnUiThread(new Runnable() {
    @Override
    public void run() {
        showEditor();
    }
});
        } catch (IOException e) {
        }
        return;
    }
    try {
        if (s2005) {
        stageDataCopy = null;
        stageData = Shell.sInstance.getContentResolver().openInputStream(uri);
        final Handler handler = new Handler(Looper.getMainLooper());
            handler.post(new Runnable() {
                @Override
                public void run() {
        Toast.makeText(Shell.sInstance, "2005 levels loaded", Toast.LENGTH_SHORT).show();
        }
        });
        loadRoom2005(currentmap);
        return;
        }
        InputStream is = Shell.sInstance.getContentResolver().openInputStream(uri);
        BufferedInputStream bis = new BufferedInputStream(is);
        bis.mark(4096); // marca antes de ler cabeçalho
        DataInputStream dis = new DataInputStream(bis);

        int numRooms = dis.readInt();

        if (numRooms > 0 && numRooms < 1000) {
            // Formato novo com cabeçalho
            roomsize = new int[numRooms];
            for (int i = 0; i < numRooms; i++) {
                roomsize[i] = dis.readInt();
            }
            roomindex = new int[numRooms];
            int runningIndex = 0;
            for (int i = 0; i < numRooms; i++) {
                roomindex[i] = runningIndex;
                runningIndex += roomsize[i];
            }
            int totalSize = runningIndex;
            realmap = new byte[totalSize];
            dis.readFully(realmap);
            
            for (int i = 0; i < numRooms; i++) {
            life[i] = dis.readInt();
            BlockPics[i] = dis.readUTF();
            EdgePics[i] = dis.readUTF();
            ExtrasPic[i] = dis.readUTF();
            StripPics[i] = dis.readUTF();
            TilesPics[i] = dis.readUTF();
            stagename[i] = dis.readUTF();
            actNumber[i] = dis.readInt();
            currentmusic[i] = dis.readUTF();
            }
            bits16 = true;
            
            final Handler handler = new Handler(Looper.getMainLooper());
            handler.post(new Runnable() {
                @Override
                public void run() {
            Toast.makeText(Shell.sInstance, "16bits", Toast.LENGTH_SHORT).show();
            }
            });
            loadRoom16bits(currentmap);
        } else {
            // Formato antigo (sem cabeçalho)
            bis.reset(); // volta para início
            bits16 = false;
            
            final Handler handler = new Handler(Looper.getMainLooper());
            handler.post(new Runnable() {
                @Override
                public void run() {
            Toast.makeText(Shell.sInstance, "8bits", Toast.LENGTH_SHORT).show();
            }
            });
            loadRoomOriginal(currentmap);
        }

        final Handler handler = new Handler(Looper.getMainLooper());
            handler.post(new Runnable() {
                @Override
                public void run() {
        Toast.makeText(Shell.sInstance, "File loaded successfully", Toast.LENGTH_SHORT).show();
        }
        });
        Shell.sInstance.runOnUiThread(new Runnable() {
    @Override
    public void run() {
        showEditor();
    }
});

    } catch (IOException e) {
        e.printStackTrace();
        Toast.makeText(Shell.sInstance, "Error loading file", Toast.LENGTH_SHORT).show();
    }
}
    
    /*public void saveMapAsTxt(Uri uri) {
    updateRealMapFromObjects(currentmap);
    try (OutputStream os = getContentResolver().openOutputStream(uri);
         OutputStreamWriter writer = new OutputStreamWriter(os)) {

        for (int room = 0; room < rooms.length; room++) {
            writer.write((!rooms[room].equals("Room 1-1") ? "\n" : "") + rooms[room] + "\n");
            int offset = roomindex[room];
            int size = roomsize[room];
            for (int i = 0; i < size; i += 2) {
                int v = realmap[offset + i];
                int y = realmap[offset + i + 1];
                int v2 = v + 128;
                int type = v2 >> 4;
                int x = v2 & 15;
                if (y < 0) y += 256;
                writer.write("Type=" + type + ", X=" + x + ", Y=" + y + "\n");
            }
        }

        Toast.makeText(Shell.sInstance, "TXT saved successfully", Toast.LENGTH_SHORT).show();

    } catch (IOException e) {
        e.printStackTrace();
        Toast.makeText(Shell.sInstance, "Error saving TXT", Toast.LENGTH_SHORT).show();
    }
}*/

    /*public void saveMapFile2(Uri uri) {
        if (uri == null) {
            Toast.makeText(Shell.sInstance, "File URI not defined", Toast.LENGTH_SHORT).show();
            return;
        }
    updateRealMapFromObjects(currentmap);
        OutputStream os = null;
        try {
            os = getContentResolver().openOutputStream(uri);
            if (os == null) {
                Toast.makeText(Shell.sInstance, "Could not open file to save", Toast.LENGTH_SHORT).show();
                return;
            }
            os.write(realmap);
            Toast.makeText(Shell.sInstance, "File saved successfully", Toast.LENGTH_SHORT).show();
        } catch (IOException e) {
            e.printStackTrace();
            Toast.makeText(Shell.sInstance, "Error saving file", Toast.LENGTH_SHORT).show();
        } finally {
            if (os != null) {
                try {
                    os.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }*/

    public void showEditor() {
        mapView = new View(Shell.sInstance) {
        public float scale = 1.0f;
        public ScaleGestureDetector scaleDetector;
        {
        scaleDetector = new ScaleGestureDetector(getContext(), new ScaleGestureDetector.SimpleOnScaleGestureListener() {
    @Override
    public boolean onScaleBegin(ScaleGestureDetector detector) {
        isScaling = true; // começa o pinch
        isScaling2 = true;
        return true;
    }
    @Override
    public boolean onScale(ScaleGestureDetector detector) {
    float prevScale = scale;
    scale *= detector.getScaleFactor();
    scale = Math.max(0.5f, Math.min(scale, 3.0f));

    // Corrige a posição para manter o ponto de foco fixo na tela
    float focusX = detector.getFocusX();
    float focusY = detector.getFocusY();

    /*offsetX += (focusX - offsetX) * (1 - scale / prevScale);
    offsetY += (focusY - offsetY) * (1 - scale / prevScale);
    */
    
    float focusXWorld = (focusX - offsetX) / prevScale;
    float focusYWorld = (focusY - offsetY) / prevScale;

    offsetX = focusX - focusXWorld * scale;
    offsetY = focusY - focusYWorld * scale;
    invalidate();
    return true;
    }
    @Override
    public void onScaleEnd(ScaleGestureDetector detector) {
        isScaling = false; // termina o pinch
        isScaling2 = true;
    }
});
}
            @Override
            protected void onDraw(Canvas canvas) {
                super.onDraw(canvas);
                canvas.drawColor(bcolor);
                Paint paint = new Paint();
                paint.setColor(Color.LTGRAY);
                paint.setStrokeWidth(1);
                /*for (int x = 0; x <= getWidth(); x += tileSize * scale) {
                canvas.drawLine(x, 0, x, getHeight(), paint);
                }
                for (int y = 0; y <= getHeight(); y += tileSize * scale) {
                canvas.drawLine(0, y, getWidth(), y, paint);
                }*/
float newSpacing = tileSize * scale;
for (float x = offsetX % newSpacing; x <= getWidth(); x += newSpacing) {
    float worldX = (x - offsetX) / newSpacing;
    if (worldX < 0 || worldX > 16) continue;
    canvas.drawLine(x, 0, x, getHeight(), paint);
}
for (float y = offsetY % newSpacing; y <= getHeight(); y += newSpacing) {
    float worldY = (y - offsetY) / newSpacing;
    if (worldY < 0 || worldY > 65535) continue;
    canvas.drawLine(0, y, getWidth(), y, paint);
}
                canvas.save();
                canvas.translate(offsetX, offsetY);
                canvas.scale(scale, scale);

                for (MapObject obj : objects) {
                    if (obj.x < 0 || obj.x > 15 || obj.y < 0 || obj.y > 65535) {
        // Ignorar objetos fora do limite válido
        continue;
    }
                    //paint.setColor(getColorByType(obj.tipo));
                    int px = obj.x * tileSize;
                    int py = obj.y * tileSize;
                    /*canvas.drawRect(px, py, px + tileSize, py + tileSize, paint);
                    paint.setColor(Color.WHITE);
                    paint.setTextSize(20);
                    canvas.drawText(obj.tipo + "," + obj.x + "," + obj.y, px + 4, py + 24, paint);*/
paint.setColor(Color.WHITE);
paint.setTextSize(16);
//canvas.drawText("Type " + obj.tipo, px + 4, py - 4, paint);
//paint.setColor(getColorByType(obj.tipo));
canvas.drawText("Type " + obj.tipo, px + 4, py - 4, paint);
try {
int stage = Math.min(currentmap / 3 + 1, 6);
InputStream is = Shell.sInstance.getAssets().open("type_" + stage + "-" + obj.tipo + ".png");
Bitmap bmp = BitmapFactory.decodeStream(is);
is.close();
if (bmp != null) {
    canvas.drawBitmap(bmp, px, py, null);
}
} catch (IOException e) {
try {
InputStream is = Shell.sInstance.getAssets().open("type_" + obj.tipo + ".png");
Bitmap bmp = BitmapFactory.decodeStream(is);
is.close();
if (bmp != null) {
    canvas.drawBitmap(bmp, px, py, null);
}
} catch (IOException e2) {
    paint.setColor(getColorByType(obj.tipo));
    canvas.drawRect(px, py, px + tileSize, py + tileSize, paint);
}
}
//canvas.drawRect(px, py, px + tileSize, py + tileSize, paint);
paint.setColor(Color.WHITE);
paint.setTextSize(16);
canvas.drawText("X:" + obj.x + ", " + "Y:" + obj.y, px + 4, py + 48, paint);
                }

                canvas.restore();
            }

            @Override
            public boolean onTouchEvent(MotionEvent event) {
                scaleDetector.onTouchEvent(event);
                switch (event.getAction()) {
                    case MotionEvent.ACTION_DOWN:
                    if (isScaling) break;
                        lastTouchX = event.getX();
                        lastTouchY = event.getY();
                        isDragging = false;
                        break;

                    case MotionEvent.ACTION_MOVE:
                    if (isScaling) break;
                    if (isScaling2) {
                    break;
                    }
                        float dx = event.getX() - lastTouchX;
                        float dy = event.getY() - lastTouchY;
                        if (Math.abs(dx) > 10 || Math.abs(dy) > 10) {
                            offsetX += dx;
                            offsetY += dy;
                            lastTouchX = event.getX();
                            lastTouchY = event.getY();
                            isDragging = true;
                            invalidate();
                        }
                        break;
                    case MotionEvent.ACTION_UP:
                        if (isScaling) break;
                        if (isScaling2) { 
                    isScaling2 = false;
                    break;
                    }
                        if (!isDragging) {
                            /*int cx = (int) ((event.getX() - offsetX) / tileSize);
                            int cy = (int) ((event.getY() - offsetY) / tileSize);*/
                            int cx = (int) ((event.getX() - offsetX) / (tileSize * scale));
                            int cy = (int) ((event.getY() - offsetY) / (tileSize * scale));
                            for (MapObject obj : objects) {
                                if (obj.x == cx && obj.y == cy) {
                                    editObjectDialog(obj);
                                    return true;
                                }
                            }
                            addObjectDialog(cx, cy);
                            }
                        break;
                }
                return true;
            }
        };
        
        selectRoomButton = new Button(Shell.sInstance);
        selectRoomButton.setText("Select Room");
        selectRoomButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                new AlertDialog.Builder(Shell.sInstance)
                        .setTitle("Choose the Room")
                        .setItems(rr, new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                currentmap = which;
                                offsetX = 0;
                                offsetY = 0;
                                if (bits16) {
                                loadRoom16bits(currentmap);
                                } else if (s2005) {
                                loadRoom2005(currentmap);
                                } else {
                                loadRoomOriginal(currentmap);
                                }
                                setLabel();
                                mapView.invalidate();
                            }
                        })
                        .show();
            }
        });

        loadButton = new Button(Shell.sInstance);
        loadButton.setText("Load Map");
        loadButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                selectMapFile();
            }
        });
        
        saveButton = new Button(Shell.sInstance);
        saveButton.setText("Save Map");
        saveButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                saveMapFile();
            }
        });
        
        resetButton = new Button(Shell.sInstance);
        resetButton.setText("Reset Map");
        resetButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (objects != null) objects.clear();
            }
        });
        
        ambientButton = new Button(Shell.sInstance);
        ambientButton.setText("Ambient Music");
        ambientButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
            try {
                /*if (mediaPlayer.isPlaying()) {
                   mediaPlayer.pause();
                   return;
                }
                mediaPlayer.reset();
                amusic = amusic.equals("menu") ? "menu2" : amusic.equals("menu2") ? "menu3" : "menu";
                AssetFileDescriptor afd = getAssets().openFd(amusic + ".mp3");
                mediaPlayer.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
                afd.close();
                mediaPlayer.prepare();
                mediaPlayer.start();*/
                amusic = amusic.equals("menu") ? "menu2" : amusic.equals("menu2") ? "menu3" : "menu";
                Intent intent = new Intent(Shell.sInstance, PlayerService.class);
intent.setAction("ACTION_PLAY_AND_RESET");
intent.putExtra("asset", amusic + ".mp3");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    Shell.sInstance.startForegroundService(intent);
} else {
    Shell.sInstance.startService(intent);
}
                } catch (Exception e) {
                }
            }
        });
        
        bkButton = new Button(Shell.sInstance);
        bkButton.setBackgroundColor(bcolor);
        bkButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                bcolor = cores[indiceCor[0]];
                bkButton.setBackgroundColor(cores[indiceCor[0]]);
                indiceCor[0] = (indiceCor[0] + 1) % cores.length;
                mapView.invalidate();
            }
        });
        
        plButton = new Button(Shell.sInstance);
        plButton.setText("Play Map");
        plButton.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        try {
            if (currentFileUri != null) {
                String displayName = "";

                Cursor cursor = Shell.sInstance.getContentResolver()
                        .query(currentFileUri, null, null, null, null);
                if (cursor != null && cursor.moveToFirst()) {
                    int index = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
                    if (index >= 0) {
                        displayName = cursor.getString(index);
                    }
                    cursor.close();
                }

                InputStream in = Shell.sInstance.getContentResolver()
                        .openInputStream(currentFileUri);
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                byte[] buffer = new byte[2048];
                int len;
                while ((len = in.read(buffer)) != -1) {
                    baos.write(buffer, 0, len);
                }

                byte[] bytes = baos.toByteArray();
                
                Shell.sInstance.getCanvas().OnlineIn = new ByteArrayInputStream(bytes);
                Shell.sInstance.getCanvas().OnlineIn.mark(Shell.sInstance.getCanvas().OnlineIn.available());
                
                ByteArrayInputStream bais = null;
                boolean z = displayName.contains(".txt");
                if (z) {
                bais = new ByteArrayInputStream(bytes);
                }

                if (!displayName.isEmpty()) {
                    Shell.sInstance.getCanvas()
                            .loadMapFromAssets(z, bais);

                    Game_Draw.browser = false;
                    Game_Draw.Online = true;

                    Shell.sInstance.setContentView(
                        Shell.sInstance.getCanvas().frameLayout != null
                            ? Shell.sInstance.getCanvas().frameLayout
                            : Shell.sInstance.getCanvas()
                    );
                    
                    if (bais != null && in != null) {
                    bais.close();
                    in.close();
                    }

                    Shell.sInstance.stopService(new Intent(
                            Shell.sInstance, PlayerService.class));
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
});
        
        configButton = new Button(Shell.sInstance);
        configButton.setText("Config");
        configButton.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        // Builder do AlertDialog
        AlertDialog.Builder builder = new AlertDialog.Builder(Shell.sInstance);
        builder.setTitle("Configuration");
        
        // Layout vertical para os 3 elementos
        LinearLayout layout = new LinearLayout(Shell.sInstance);
        layout.setOrientation(LinearLayout.VERTICAL);

        // Campo Life (EditText)
        final EditText lifeInput = new EditText(Shell.sInstance);
        lifeInput.setHint("Life Count");
        lifeInput.setBackgroundColor(Color.parseColor("#FF8C00"));
        lifeInput.setTextColor(Color.BLACK);
        lifeInput.setInputType(InputType.TYPE_CLASS_NUMBER);
        layout.addView(lifeInput);
        
        final EditText stg = new EditText(Shell.sInstance);
        stg.setHint("Stage Name");
        stg.setBackgroundColor(Color.parseColor("#FF8C00"));
        stg.setTextColor(Color.BLACK);
        layout.addView(stg);
        
        final EditText an = new EditText(Shell.sInstance);
        an.setHint("Act Number");
        an.setBackgroundColor(Color.parseColor("#FF8C00"));
        an.setTextColor(Color.BLACK);
        an.setInputType(InputType.TYPE_CLASS_NUMBER);
        layout.addView(an);

        // Botão Background
        final Button BPButton = new Button(Shell.sInstance);
        BPButton.setText("BlockPics");
        BPButton.setBackgroundColor(Color.parseColor("#FFA500")); // Laranja
        BPButton.setTextColor(Color.BLACK);
        layout.addView(BPButton);

        // Botão Objects
        final Button EDButton = new Button(Shell.sInstance);
        EDButton.setText("EdgePics");
        EDButton.setBackgroundColor(Color.parseColor("#FFA500")); // Laranja
        EDButton.setTextColor(Color.BLACK);
        layout.addView(EDButton);
        
        final Button EXButton = new Button(Shell.sInstance);
        EXButton.setText("ExtrasPic");
        EXButton.setBackgroundColor(Color.parseColor("#FFA500")); // Laranja
        EXButton.setTextColor(Color.BLACK);
        layout.addView(EXButton);
        
        final Button STButton = new Button(Shell.sInstance);
        STButton.setText("StripPics");
        STButton.setBackgroundColor(Color.parseColor("#FFA500")); // Laranja
        STButton.setTextColor(Color.BLACK);
        layout.addView(STButton);
        
        final Button TLButton = new Button(Shell.sInstance);
        TLButton.setText("TilesPics");
        TLButton.setBackgroundColor(Color.parseColor("#FFA500")); // Laranja
        TLButton.setTextColor(Color.BLACK);
        layout.addView(TLButton);
        
        cb.setText("Custom Music");
        ViewGroup parent = (ViewGroup) cb.getParent();
        if (parent != null) parent.removeView(cb);
        layout.addView(cb);

        builder.setView(layout);

        // Variáveis finais para salvar os valores
        final String[] selected = new String[5];

        // Listeners para os botões que abrem lista de arquivos
        BPButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                listarArquivos("BlockPics/", new FileSelectCallback() {
                    @Override
                    public void onFileSelected(String fileName) {
                        selected[0] = fileName;
                        BPButton.setText(fileName);
                    }
                });
            }
        });

        EDButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                listarArquivos("EdgePics/", new FileSelectCallback() {
                    @Override
                    public void onFileSelected(String fileName) {
                        selected[1] = fileName;
                        EDButton.setText(fileName);
                    }
                });
            }
        });
        
        EXButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                listarArquivos("ExtrasPic/", new FileSelectCallback() {
                    @Override
                    public void onFileSelected(String fileName) {
                        selected[2] = fileName;
                        EXButton.setText(fileName);
                    }
                });
            }
        });
        
        STButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                listarArquivos("StripPics/", new FileSelectCallback() {
                    @Override
                    public void onFileSelected(String fileName) {
                        selected[3] = fileName;
                        STButton.setText(fileName);
                    }
                });
            }
        });
        
        TLButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                listarArquivos("TilesPics/", new FileSelectCallback() {
                    @Override
                    public void onFileSelected(String fileName) {
                        selected[4] = fileName;
                        TLButton.setText(fileName);
                    }
                });
            }
        });

        // Botão OK
        builder.setPositiveButton("OK", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialogInterface, int i) {
                String text = lifeInput.getText().toString().trim();
                if (!text.isEmpty()) {
                life[currentmap] = Integer.parseInt(text);
                } else {
                life[currentmap] = 2;
                }
                text = stg.getText().toString().trim();
                if (!text.isEmpty()) {
                stagename[currentmap] = text;
                } else {
                stagename[currentmap] = null;
                }
                text = an.getText().toString().trim();
                if (!text.isEmpty()) {
                actNumber[currentmap] = Integer.parseInt(text);
                } else {
                actNumber[currentmap] = -1;
                }
                BlockPics[currentmap] = selected[0];
                EdgePics[currentmap] = selected[1];
                ExtrasPic[currentmap] = selected[2];
                StripPics[currentmap] = selected[3];
                TilesPics[currentmap] = selected[4];
                cb.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
    @Override
    public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
        if (isChecked) {
            custommusic = true;
        } else {
            custommusic = false;
        }
    }
});
            }
        });

        builder.setNegativeButton("Cancel", null);
        AlertDialog dialog = builder.create();
        dialog.show();
        dialog.getWindow().setBackgroundDrawable(new ColorDrawable(Color.YELLOW));
    }
});
        
        Button musicButton = new Button(Shell.sInstance);
musicButton.setText("Music Controls");

final String[] currentUrl = {null};
final boolean[] isPaused = {false};

musicButton.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        String[] options = {"Select Music", "Restart Music"};
        
        new AlertDialog.Builder(Shell.sInstance)
            .setTitle("Music Options")
            .setItems(options, new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    switch (which) {
                        case 0: // Select Music
                            // Abre lista para escolher música
                            new AlertDialog.Builder(Shell.sInstance)
    .setTitle("Choose the Group")
    .setItems(nomesGrupos.toArray(new String[0]), new DialogInterface.OnClickListener() {
        @Override
        public void onClick(DialogInterface dialog, final int whichGroup) {
            if (nomes != null && nomes.size() != 0 && nomes.size() > whichGroup) {
            ArrayList<String> musicasDoGrupo = nomes.get(whichGroup);

            new AlertDialog.Builder(Shell.sInstance)
                .setTitle("Choose the Music")
                .setItems(musicasDoGrupo.toArray(new String[0]), new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog2, int whichMusic) {
                        try {
                            /*if (mediaPlayer.isPlaying() || isPaused[0]) {
                                mediaPlayer.stop();
                            }
                            mediaPlayer.reset();*/
                            Intent intent = new Intent(Shell.sInstance, PlayerService.class);
intent.setAction("ACTION_RESET");
intent.putExtra("url", "");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    Shell.sInstance.startForegroundService(intent);
} else {
    Shell.sInstance.startService(intent);
}

                            final String url = mp3Links.get(whichGroup).get(whichMusic);
                            currentUrl[0] = url;
                            if (whichGroup == 2) {
                            new Thread(new Runnable() {
    @Override
    public void run() {
        try {
            final File outF = downloadAndPlayWaybackMachine(url);
            if (outF == null) {
                Shell.sInstance.runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        Toast.makeText(Shell.sInstance, "Error downloading file", Toast.LENGTH_LONG).show();
                    }
                });
                return;
            }
            
            Intent intent = new Intent(Shell.sInstance, PlayerService.class);
intent.setAction("ACTION_PLAY");
intent.putExtra("url", outF.getAbsolutePath());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    Shell.sInstance.startForegroundService(intent);
} else {
    Shell.sInstance.startService(intent);
}

            /*mediaPlayer.reset();
            mediaPlayer.setDataSource(outF.getAbsolutePath());

            mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
                @Override
                public void onPrepared(MediaPlayer mp) {
                    mp.start();
                }
            });
            
            mediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {
    @Override
    public boolean onError(MediaPlayer mp, int what, int extra) {
    final int what2 = what;
    final int extra2 = extra;
        Shell.sInstance.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                Toast.makeText(Shell.sInstance, "MediaPlayer error: " + what2 + ", " + extra2, Toast.LENGTH_LONG).show();
            }
        });
        return true;
    }
});

            mediaPlayer.prepareAsync();*/

        } catch (final Exception e) {
            e.printStackTrace();
            Shell.sInstance.runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    Toast.makeText(Shell.sInstance, "Error playing file: " + e.getMessage(), Toast.LENGTH_LONG).show();
                }
            });
        }
    }
}).start();
//isPaused[0] = false;
return;
                            } else {
                                new Thread(new Runnable() {
    @Override
    public void run() {
                            currentmusic[currentmap] = url;
                            Intent intent = new Intent(Shell.sInstance, PlayerService.class);
intent.setAction("ACTION_PLAY");
intent.putExtra("url", url);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    Shell.sInstance.startForegroundService(intent);
} else {
    Shell.sInstance.startService(intent);
}
                            /*mediaPlayer.setDataSource(url);
                            mediaPlayer.prepare();
                            mediaPlayer.start();*/
                            //isPaused[0] = false;
                            }
                            }).start();
                            }
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                })
                .show();
                }
        }
    })
    .show();
                            break;

                        case 1: // Restart Music.
                        Intent intent = new Intent(Shell.sInstance, PlayerService.class);
intent.setAction("ACTION_RESTART");
intent.putExtra("url", "");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    Shell.sInstance.startForegroundService(intent);
} else {
    Shell.sInstance.startService(intent);
}
                            /*if (mediaPlayer.isPlaying()) {
                                mediaPlayer.pause();
                                isPaused[0] = true;
                            } else {
                                mediaPlayer.start();
                                isPaused[0] = false;
                            }*/
                            break;
                    }
                }
            })
            .show();
    }
});
        Button userRButton = new Button(Shell.sInstance);
        userRButton.setText("User Repository");
        Button pButton = new Button(Shell.sInstance);
        pButton.setText("\u2630");
        pButton.setTextSize(TypedValue.COMPLEX_UNIT_SP, 24);
        musicpsButton = new Button(Shell.sInstance);
        musicButtonText(false);
        musicpsButton.setTextSize(TypedValue.COMPLEX_UNIT_SP, 24);
        
        TableLayout tableLayout = new TableLayout(Shell.sInstance);

        final TableRow row1 = new TableRow(Shell.sInstance);
        final TableRow row2 = new TableRow(Shell.sInstance);
        final TableRow row3 = new TableRow(Shell.sInstance);
        final TableRow row4 = new TableRow(Shell.sInstance);

        //TableRow.LayoutParams params = new TableRow.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1.0f);
        
        int sizeDp = 60;
        float scale = Shell.sInstance.getResources().getDisplayMetrics().density;
        int sizePx = (int)(sizeDp * scale + 0.5f);
        TableRow.LayoutParams params = new TableRow.LayoutParams(0, sizePx, 1.0f);
        Button[] buttons = {
            musicButton, selectRoomButton, saveButton, loadButton,
            ambientButton, bkButton, configButton, resetButton,
            userRButton, musicpsButton, pButton, plButton
        };
        for (Button b : buttons) {
            b.setLayoutParams(params);
            b.setMinWidth(sizePx);
            b.setMinHeight(sizePx);
        }
        
        userRButton.setLayoutParams(params);
        musicpsButton.setLayoutParams(params);
        pButton.setLayoutParams(params);
        label = new TextView(Shell.sInstance);
        label.setLayoutParams(params);
        setLabel();

        row1.addView(musicButton);
        row1.addView(ambientButton);
        row1.addView(plButton);
        row2.addView(userRButton);
        row2.addView(selectRoomButton);
        row2.addView(configButton);
        row3.addView(saveButton);
        row3.addView(loadButton);
        row3.addView(resetButton);
        row4.addView(pButton);
        row4.addView(label);
        row4.addView(musicpsButton);
        row4.addView(bkButton);
        tableLayout.addView(row1);
        tableLayout.addView(row2);
        tableLayout.addView(row3);
        tableLayout.addView(row4);

        mainLayout = new LinearLayout(Shell.sInstance);
        mainLayout.setOrientation(LinearLayout.VERTICAL);
        mainLayout.addView(tableLayout);
        //mainLayout.addView(resetButton);
        
        userRButton.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        AlertDialog.Builder builder = new AlertDialog.Builder(Shell.sInstance);
        builder.setTitle("Configuration");
        LinearLayout layout = new LinearLayout(Shell.sInstance);
        layout.setOrientation(LinearLayout.VERTICAL);
        final EditText linkUser = new EditText(Shell.sInstance);
        linkUser.setHint("Repository link");
        linkUser.setBackgroundColor(Color.parseColor("#FF8C00"));
        linkUser.setTextColor(Color.BLACK);
        layout.addView(linkUser);
        builder.setView(layout);
        builder.setNegativeButton("Cancel", null);
        builder.setPositiveButton("OK", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialogInterface, int i) {
                userlink = linkUser.getText().toString().trim();
                if (!userlink.endsWith("/")) userlink = userlink + "/";
            }
        });
        AlertDialog dialog = builder.create();
        dialog.show();
        dialog.getWindow().setBackgroundDrawable(new ColorDrawable(Color.YELLOW));
                    }
                    });
        pButton.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
                    if (!hide) {
                    row1.setVisibility(View.GONE);
                    row2.setVisibility(View.GONE);
                    row3.setVisibility(View.GONE);
                    hide = true;
                    } else {
                    row1.setVisibility(View.VISIBLE);
                    row2.setVisibility(View.VISIBLE);
                    row3.setVisibility(View.VISIBLE);
                    hide = false;
                    }
                    }
                    });
        musicpsButton.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
                    Intent intent = new Intent(Shell.sInstance, PlayerService.class);
intent.setAction("ACTION_PAUSE");
intent.putExtra("url", "");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    Shell.sInstance.startForegroundService(intent);
} else {
    Shell.sInstance.startService(intent);
}
                    }
                    });
                    
        mainLayout.addView(mapView);
        Game_Draw.browser = true;
        Shell.sInstance.runOnUiThread(new Runnable() {
        @Override
        public void run() {
        Shell.sInstance.setContentView(mainLayout);
        }
        });
    }
    
    public void setLabel() {
    label.setText("Room " + (!s2005 ? (currentmap / 3 + 1) + "-" + (currentmap % 3 + 1) : currentmap));
    }

public File downloadAndPlayWaybackMachine(String fileURL) {
    HttpURLConnection pageConn = null;
    HttpURLConnection fileConn = null;

    try {
        // 1) Conexão para obter HTML
        URL pageUrl = new URL(fileURL);
        pageConn = (HttpURLConnection) pageUrl.openConnection();
        pageConn.setRequestProperty("Accept-Encoding", "identity");
        pageConn.setConnectTimeout(15000);
        pageConn.setReadTimeout(30000);
        pageConn.connect();

        if (pageConn.getResponseCode() / 100 != 2) {
            throw new IOException("HTTP error page: " + pageConn.getResponseCode());
        }

        StringBuilder html = new StringBuilder();
        try (BufferedReader reader = new BufferedReader(
                new InputStreamReader(pageConn.getInputStream()))) {
            String line;
            while ((line = reader.readLine()) != null) {
                html.append(line).append("\n");
            }
        }

        // 2) Extrair link real do MP3
        Pattern p = Pattern.compile(
            "<iframe[^>]*id=[\"']playback[\"'][^>]*src=[\"']([^\"']+)[\"']",
            Pattern.CASE_INSENSITIVE);
        Matcher m = p.matcher(html.toString());
        if (!m.find()) {
            throw new IOException("No mp3 links found");
        }
        String mp3Path = m.group(1);
        URL mp3Url = new URL(pageUrl, mp3Path);
        currentmusic[currentmap] = mp3Url.toString();

        pageConn.disconnect();

        // 3) Conexão para baixar o MP3
        fileConn = (HttpURLConnection) mp3Url.openConnection();
        fileConn.setRequestProperty("Accept-Encoding", "identity");
        fileConn.setInstanceFollowRedirects(true);
        fileConn.setConnectTimeout(30000);
        fileConn.setReadTimeout(120000);
        fileConn.connect();

        int response = fileConn.getResponseCode();
        if (response / 100 == 3) {
            String redirect = fileConn.getHeaderField("Location");
            fileConn.disconnect();
            mp3Url = new URL(redirect);
            fileConn = (HttpURLConnection) mp3Url.openConnection();
            fileConn.setRequestProperty("Accept-Encoding", "identity");
            fileConn.setConnectTimeout(15000);
            fileConn.setReadTimeout(60000);
            fileConn.connect();
        }

        if (fileConn.getResponseCode() / 100 != 2) {
            throw new IOException("HTTP error mp3: " + fileConn.getResponseCode());
        }

        int expected = fileConn.getContentLength();
        int total = 0;

        File outputFile = new File(Shell.sInstance.getExternalFilesDir(null), "temp_audio.mp3");
        if (outputFile.exists()) outputFile.delete();

        try (InputStream input = new BufferedInputStream(fileConn.getInputStream());
             FileOutputStream output = new FileOutputStream(outputFile)) {

            byte[] buffer = new byte[32 * 1024]; // 32 KB buffer
            int bytesRead;
            while ((bytesRead = input.read(buffer)) != -1) {
                output.write(buffer, 0, bytesRead);
                total += bytesRead;
            }
            output.flush();
        }

        if (expected > 0 && total != expected) {
            throw new IOException("Incomplete file: expected " + expected + ", downloaded " + total);
        }

        return outputFile;

    } catch (Exception e) {
        e.printStackTrace();
        final Exception e2 = e;
        Shell.sInstance.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                Toast.makeText(Shell.sInstance, "Error: " + e2.getMessage(), Toast.LENGTH_LONG).show();
            }
        });
    } finally {
        if (pageConn != null) pageConn.disconnect();
        if (fileConn != null) fileConn.disconnect();
    }

    return null;
}

interface FileSelectCallback {
    void onFileSelected(String fileName);
}

// Método para listar arquivos de assets com prefixo
public void listarArquivos(String prefix, final FileSelectCallback callback) {
    try {
        List<String> filtered = new ArrayList<>();
        listarRecursivo("", prefix, filtered);
        
        List<String> remotos = lerListaRemota(defaulthttp);
        List<String> remotos2 = lerListaRemota(userlink);
        if (remotos != null) filtered.addAll(filtrarPorPrefixo(remotos, prefix, defaulthttp));
        if (remotos2 != null) filtered.addAll(filtrarPorPrefixo(remotos2, prefix, userlink));

        if (filtered.isEmpty()) return;

        final List<String> f2 = filtered;

        AlertDialog.Builder listBuilder = new AlertDialog.Builder(Shell.sInstance);
        listBuilder.setTitle("Choose file");

        ListView listView = new ListView(Shell.sInstance);
        listView.setAdapter(new FileImageAdapter(Shell.sInstance, f2));

        final AlertDialog dialog = listBuilder.setView(listView).create();
        dialog.show();

        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                callback.onFileSelected(f2.get(position));
                dialog.dismiss();
            }
        });

    } catch (IOException e) {
        e.printStackTrace();
    }
}

public void listarRecursivo(String path, String prefix, List<String> out) throws IOException {
    String[] files = Shell.sInstance.getAssets().list(path);
    if (files == null) return;

    for (String file : files) {
        String fullPath = path.isEmpty() ? file : path + "/" + file;
        String[] children = Shell.sInstance.getAssets().list(fullPath);
        if (children == null || children.length == 0) {
            // É arquivo
            if (fullPath.startsWith(prefix)) {
                out.add(fullPath);
            }
        } else {
            // É diretório
            listarRecursivo(fullPath, prefix, out);
        }
    }
}

public List<String> lerListaRemota(String s) {
    List<String> lista = new ArrayList<>();
    try {
        URL url = new URL(s + "config.ini");
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
        conn.setRequestMethod("GET");
        conn.setConnectTimeout(5000);
        conn.setReadTimeout(5000);

        BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
        String line;
        while ((line = reader.readLine()) != null) {
            if (!line.trim().isEmpty()) {
                lista.add(line.trim());
            }
        }
        reader.close();
        conn.disconnect();
    } catch (Exception e) {
        e.printStackTrace();
    }
    return lista;
}

public List<String> filtrarPorPrefixo(List<String> lista, String prefix, String s) {
    List<String> filtrados = new ArrayList<>();
    for (String nome : lista) {
        if (nome.startsWith(prefix)) {
            filtrados.add(s + nome);
        }
    }
    return filtrados;
}
    
    public void loadRoomOriginal(int roomId) {
    maxTipo = -1;
    if (clear) objects.clear();
    int start = roomindex[roomId];
    int byteCount = roomsize[roomId];
    if (start + byteCount > realmap.length) return;
    int objectCount = byteCount / 2;
    int index = start;
    int prevYRaw = -1;
    boolean addOffset = false;
    for (int i = 0; i < objectCount; i++) {
        int v = realmap[index++];
        int y = realmap[index++];
        if (y < 0) y += 256;
        if (!addOffset && prevYRaw != -1 && y < prevYRaw) {
            addOffset = true;
        }
        prevYRaw = y;
        if (addOffset) y += 256;
        int v2 = v + 128;
        int tipo = v2 >> 4;
        int x = v2 & 15;
        objects.add(new MapObject(tipo, x, y, roomId));
    }
}
    
    public void loadRoom16bits(int roomId) {
    maxTipo = -1;
    if (clear) objects.clear();
    int start = roomindex[roomId];
    int byteCount = roomsize[roomId];

    if (start + byteCount > realmap.length || byteCount % 3 != 0) return;

    int objectCount = byteCount / 3;
    int index = start;

    for (int i = 0; i < objectCount; i++) {
        int v = realmap[index++];
        int yHigh = realmap[index++] & 0xFF;
        int yLow = realmap[index++] & 0xFF;
        int v2 = v + 128;
        int tipo = v2 >> 4;
        int x = v2 & 15;
        int y = (yHigh << 8) | yLow;

        objects.add(new MapObject(tipo, x, y, roomId));
    }
}

    public void loadRoom2005(int roomId) {
    maxTipo = -1;
    if (clear) objects.clear();

    try {
        // Clona stageData para memória
        if (stageDataCopy == null) {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        byte[] buffer = new byte[4096];
        int n;
        while ((n = stageData.read(buffer)) != -1) {
            baos.write(buffer, 0, n);
        }
        stageDataCopy = baos.toByteArray();
        }

        // Usa clone para ler o cabeçalho
        DataInputStream dis = new DataInputStream(new ByteArrayInputStream(stageDataCopy));

        int numC = dis.readUnsignedShort();
        int numD = dis.readUnsignedShort();

        int[] c = new int[numC];
        int[] d = new int[numD + 1];

        for (int i = 0; i < c.length; i++) c[i] = dis.readUnsignedShort();
        for (int i = 0; i < d.length; i++) d[i] = dis.readInt();

        int headerSize = 2 + 2 + (2 * c.length) + (4 * d.length);
        dis.close();

        for (int tipo = 0; tipo < c.length; tipo++) {
            int baseIndex = c[tipo] + roomId;
            if (baseIndex < 0 || baseIndex + 1 >= d.length) continue;

            int start = d[baseIndex];
            int end = d[baseIndex + 1];
            int length = end - start;
            if (length <= 0) continue;

            // Novo input para o trecho binário da fase
            InputStream raw = new ByteArrayInputStream(stageDataCopy, headerSize + start, length);
            byte[] data = new byte[length];
            int readBytes = raw.read(data);
            raw.close();
            if (readBytes != length) continue;

            int total = data[0] & 0xFF;
            for (int i = 0; i < total && (4 + i * 5 + 4) < data.length; i++) {
                int base = 4 + i * 5;
                int x = ((data[base + 4] & 0xFF) << 8) | (data[base + 1] & 0xFF);
                int y = data[base + 2] & 0xFF;
                int type = data[base + 3] & 0x3F;
                objects.add(new MapObject(type, x, y, roomId));
            }
        }

        maxTipo = 64;
    } catch (Exception e) {
    final Handler handler = new Handler(Looper.getMainLooper());
            handler.post(new Runnable() {
                @Override
                public void run() {
        Toast.makeText(Shell.sInstance, "failed to load 2005 levels", Toast.LENGTH_SHORT).show();
        }
        });
    }
}

    public void addObjectDialog(final int x, final int y) {
        if (x < 0 || x > 15 || y < 0 || y > 65535) {
    //Toast.makeText(Shell.sInstance, "beyond the limit", Toast.LENGTH_SHORT).show();
    return;
}
        final String[] tipos = new String[maxTipo != -1 ? (maxTipo + 1) : 16];
        for (int i = 0; i < tipos.length; i++) {
            tipos[i] = "Type " + i;
        }
        new AlertDialog.Builder(Shell.sInstance)
                .setTitle("Add object")
                .setItems(tipos, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        objects.add(new MapObject(which, x, y, currentmap));
                        mapView.invalidate();
                    }
                })
                .show();
    }

    public void editObjectDialog(final MapObject obj) {
        final String[] options = {"Remove", "Change Type"};
        new AlertDialog.Builder(Shell.sInstance)
                .setTitle("Edit object")
                .setItems(options, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        if (which == 0) {
                            objects.remove(obj);
                            mapView.invalidate();
                        } else if (which == 1) {
                            editObjectTypeDialog(obj);
                        }
                    }
                })
                .show();
    }

    public void editObjectTypeDialog(final MapObject obj) {
        final String[] tipos = new String[maxTipo != -1 ? maxTipo : 16];
        for (int i = 0; i < tipos.length; i++) {
            tipos[i] = "Type " + i;
        }
        new AlertDialog.Builder(Shell.sInstance)
                .setTitle("Select new type")
                .setItems(tipos, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        obj.tipo = which;
                        mapView.invalidate();
                    }
                })
                .show();
    }

    /*public void updateRealMapFromObjects(int roomId) {
        int start = roomindex[roomId];
        int byteCount = roomsize[roomId];
        int objectCount = byteCount / 2;

        for (int i = 0; i < objectCount && i < objects.size(); i++) {
            MapObject obj = objects.get(i);
            int v2 = (obj.tipo << 4) + obj.x;
            int v = v2 - 128;
            realmap[start + i * 2] = (byte) v;
            realmap[start + i * 2 + 1] = (byte) obj.y;
        }
    }*/
    /*public void updateRealMapFromObjects(int roomId) {
    int start = roomindex[roomId];
    int byteCount = roomsize[roomId];
    int objectCount = byteCount / 2;

    // Limpa a área correspondente na realmap
    for (int i = 0; i < byteCount; i++) {
        realmap[start + i] = 0;
    }

    // Filtra os objetos da sala atual
    List<MapObject> objsDaSala = new ArrayList<>();
    for (MapObject obj : objects) {
        int objIndex = roomindex[roomId];
        int objEnd = objIndex + roomsize[roomId];
        int pos = objects.indexOf(obj);
        if (pos * 2 >= objIndex && pos * 2 < objEnd) {
            objsDaSala.add(obj);
        }
    }

    // Ordena por Y, depois X
    Collections.sort(objsDaSala, new Comparator<MapObject>() {
        @Override
        public int compare(MapObject a, MapObject b) {
            if (a.y != b.y) return Integer.compare(a.y, b.y);
            return Integer.compare(a.x, b.x);
        }
    });

    // Grava os objetos ordenados no realmap
    int index = start;
    for (int i = 0; i < objectCount && i < objsDaSala.size(); i++) {
        MapObject obj = objsDaSala.get(i);
        int v2 = (obj.tipo << 4) | (obj.x & 15);
        int v = v2 - 128;
        realmap[index++] = (byte) v;
        realmap[index++] = (byte) obj.y;
    }
}*/

    public void buildRealMap2AllRooms() {
    Off2 = 0;

    clear = false;
    // Zera roomsize2 e roomindex2
    for (int i = 0; i < rr.length; i++) {
    if (bits16 && currentmap != i) loadRoom16bits(i);
    else if (currentmap != i) loadRoomOriginal(i);
        roomsize2[i] = 0;
        roomindex2[i] = 0;
    }
    clear = true;

    // Primeiro passa: calcula roomsize2 total
    int totalSize = 0;
    for (int roomId = 0; roomId < rr.length; roomId++) {
        int count = 0;
        for (MapObject obj : objects) {
            if (obj.roomId == roomId) count++;
        }
        roomsize2[roomId] = (int) (count * 3); // 3 bytes por objeto
        totalSize += roomsize2[roomId];
    }

    // Aloca realmap2 de uma vez só
    realmap2 = new byte[totalSize];

    // Segundo passa: grava cada sala ordenada
    for (int roomId = 0; roomId < rr.length; roomId++) {
        roomindex2[roomId] = Off2;
        List<MapObject> objs = new ArrayList<>();
        for (MapObject obj : objects) {
            if (obj.roomId == roomId) objs.add(obj);
        }

        Collections.sort(objs, new Comparator<MapObject>() {
            @Override
            public int compare(MapObject a, MapObject b) {
                if (a.y != b.y) return Integer.compare(a.y, b.y);
                return Integer.compare(a.x, b.x);
            }
        });
        
        for (MapObject obj : objs) {
            int v2 = (obj.tipo << 4) | (obj.x & 15);
            int v = v2 - 128;
            realmap2[Off2++] = (byte) v;
            realmap2[Off2++] = (byte) ((obj.y >> 8) & 0xFF);
            realmap2[Off2++] = (byte) (obj.y & 0xFF);
        }
    }
}

    public int getColorByType(int tipo) {
        switch (tipo) {
            case 0: return Color.parseColor("#FFA500"); // Orange
case 1: return Color.GREEN;
case 2: return Color.GRAY;
case 3: return Color.YELLOW;
case 4: return Color.RED;
case 5: return Color.parseColor("#006400"); // DarkGreen
case 6: return Color.BLUE;
case 7: return Color.DKGRAY;
case 8: return Color.WHITE;
case 9: return Color.parseColor("#8B4513"); // SaddleBrown
case 10: return Color.parseColor("#B22222"); // FireBrick
case 11: return Color.parseColor("#228B22"); // ForestGreen
case 12: return Color.parseColor("#1E90FF"); // DodgerBlue
case 13: return Color.parseColor("#FFD700"); // Gold
case 14: return Color.CYAN;
case 15: return Color.MAGENTA;
default: return Color.WHITE;
        }
    }

    public void loadTxtToRealmap(Uri uri) {
    rr = s2005 ? rooms2005 : rooms;
    InputStream in = null;
    if (uri == null) {
        try {
        in = Shell.sInstance.getAssets().open("decoded_map.txt");
        } catch (IOException e) {
        }
    } else {
        try {
        in = Shell.sInstance.getContentResolver().openInputStream(uri);
        } catch (IOException e) {
        }
    }
    try (BufferedReader reader = new BufferedReader(new InputStreamReader(in))) {
        int totalSize = 0;
        int[] roomOffsets = new int[rr.length];
        int[] sizes = new int[rr.length];
        ByteArrayOutputStream baos = new ByteArrayOutputStream();

        String line;
        int roomId = -1;
        int currentWriteIndex = 0;

        while ((line = reader.readLine()) != null) {
            line = line.trim();
            if (line.isEmpty()) continue;

            if (line.startsWith("Room ")) {
                roomId++;
                currentWriteIndex = baos.size();
                roomOffsets[roomId] = (int) currentWriteIndex;
                continue;
            }

            if (line.startsWith("RoomSize=") && roomId >= 0) {
                sizes[roomId] = (int) Integer.parseInt(line.substring(9));
                continue;
            }
            
            if (line.startsWith("Life Count=") && roomId >= 0) {
                life[roomId] = Integer.parseInt(line.substring(11));
                continue;
            }
            
            if (line.startsWith("BlockPics=") && roomId >= 0) {
                BlockPics[roomId] = line.substring(10).trim();
                continue;
            }
            
            if (line.startsWith("EdgePics=") && roomId >= 0) {
                EdgePics[roomId] = line.substring(9).trim();
                continue;
            }
            
            if (line.startsWith("ExtrasPic=") && roomId >= 0) {
                ExtrasPic[roomId] = line.substring(10).trim();
                continue;
            }
            
            if (line.startsWith("StripPics=") && roomId >= 0) {
                StripPics[roomId] = line.substring(10).trim();
                continue;
            }
            
            if (line.startsWith("TilesPics=") && roomId >= 0) {
                TilesPics[roomId] = line.substring(10).trim();
                continue;
            }
            
            if (line.startsWith("StageName=") && roomId >= 0) {
                stagename[roomId] = line.substring(10).trim();
                continue;
            }
            
            if (line.startsWith("ActNumber=") && roomId >= 0) {
                actNumber[roomId] = Integer.parseInt(line.substring(10));
                continue;
            }
            
            if (line.startsWith("Music=") && roomId >= 0) {
                currentmusic[roomId] = URLDecoder.decode(line.substring(6).trim(), "utf-8");
                continue;
            }

            if (roomId < 0) continue;

            String[] parts = line.split(",");
            if (parts.length != 3) continue;

            int tipo = -1, x = -1, y = -1;
            try {
                for (String part : parts) {
                    part = part.trim();
                    if (part.startsWith("Type=")) tipo = Integer.parseInt(part.substring(5));
                    else if (part.startsWith("X=")) x = Integer.parseInt(part.substring(2));
                    else if (part.startsWith("Y=")) y = Integer.parseInt(part.substring(2));
                }
            } catch (NumberFormatException e) {
                continue;
            }

            if (tipo < 0 || x < 0 || x > 15 || y < 0 || y > 65535) continue;

            int v2 = (tipo << 4) | (x & 15);
            int v = v2 - 128;
            baos.write((byte) v);
            baos.write((byte) ((y >> 8) & 0xFF));
            baos.write((byte) (y & 0xFF));
        }

        realmap = baos.toByteArray();

        // Atualiza roomsize e roomindex com base nos dados lidos
        roomsize = sizes;
        roomindex = roomOffsets;

        bits16 = true;
        loadRoom16bits(currentmap);
        final Handler handler = new Handler(Looper.getMainLooper());
            handler.post(new Runnable() {
                @Override
                public void run() {
        Toast.makeText(Shell.sInstance, "TXT loaded in 16-bit format", Toast.LENGTH_SHORT).show();
        }
        });
        Shell.sInstance.runOnUiThread(new Runnable() {
    @Override
    public void run() {
        showEditor();
    }
});

    } catch (IOException e) {
        e.printStackTrace();
        final Handler handler = new Handler(Looper.getMainLooper());
            handler.post(new Runnable() {
                @Override
                public void run() {
        Toast.makeText(Shell.sInstance, "Error reading file", Toast.LENGTH_SHORT).show();
        }
        });
    }
}

/*public void saveMapAsTxt(Uri uri) {
    updateRealMapFromObjects(currentmap);
    try (OutputStream os = getContentResolver().openOutputStream(uri);
         OutputStreamWriter writer = new OutputStreamWriter(os)) {

        for (int room = 0; room < rooms.length; room++) {
            writer.write((!rooms[room].equals("Room 1-1") ? "\n" : "") + rooms[room] + "\nRoomSize=" + roomsize[room] + "\n");
            int offset = roomindex[room];
            int size = roomsize[room];
            for (int i = 0; i < size; i += 2) {
                int v = realmap[offset + i];
                int y = realmap[offset + i + 1];
                int v2 = v + 128;
                int type = v2 >> 4;
                int x = v2 & 15;
                if (y < 0) y += 256;
                writer.write("Type=" + type + ", X=" + x + ", Y=" + y + "\n");
            }
        }

        Toast.makeText(Shell.sInstance, "TXT saved successfully", Toast.LENGTH_SHORT).show();

    } catch (IOException e) {
        e.printStackTrace();
        Toast.makeText(Shell.sInstance, "Error saving TXT", Toast.LENGTH_SHORT).show();
    }
}*/

public void saveMapFile2(Uri uri, boolean z) {
    buildRealMap2AllRooms();
    if (z) {
    try (OutputStream os = Shell.sInstance.getContentResolver().openOutputStream(uri);
         OutputStreamWriter writer = new OutputStreamWriter(os)) {
         for (int room = 0; room < rr.length; room++) {
            StringBuilder sb = new StringBuilder();

              sb.append((!rr[room].equals("Room 1-1") ? "\n" : ""))
              .append(rr[room])
              .append("\nRoomSize=").append(roomsize2[room]).append("\n");

              if (life[room] != 0 &&
                  BlockPics[room] != null && !BlockPics[room].isEmpty() &&
                  EdgePics[room] != null && !EdgePics[room].isEmpty() &&
                  ExtrasPic[room] != null && !ExtrasPic[room].isEmpty() &&
                  StripPics[room] != null && !StripPics[room].isEmpty() &&
                  TilesPics[room] != null && !TilesPics[room].isEmpty()) {
                   sb.append("Life Count=").append(life[room]).append("\n")
                  .append("BlockPics=").append(BlockPics[room]).append("\n")
                  .append("EdgePics=").append(EdgePics[room]).append("\n")
                  .append("ExtrasPic=").append(ExtrasPic[room]).append("\n")
                  .append("StripPics=").append(StripPics[room]).append("\n")
                  .append("TilesPics=").append(TilesPics[room]).append("\n")
                  .append("StageName=").append(stagename[room]).append("\n")
                  .append("ActNumber=").append(actNumber[room]).append("\n");
              }

              if (custommusic && currentmusic[room] != null && !currentmusic[room].isEmpty()) {
              sb.append("Music=").append(URLEncoder.encode(currentmusic[room], "utf-8")).append("\n");
              }

              int offset = roomindex2[room];
              int size = roomsize2[room];
              for (int i = 0; i < size; i += 3) {
                int v = realmap2[offset + i];
                int yHigh = realmap2[offset + i + 1] & 0xFF;
                int yLow = realmap2[offset + i + 2] & 0xFF;
                int y = (yHigh << 8) | yLow;
                int v2 = v + 128;
                int type = v2 >> 4;
                int x = v2 & 15;
                sb.append("Type=").append(type)
                  .append(", X=").append(x)
                  .append(", Y=").append(y).append("\n");
              }

              writer.write(sb.toString());
            }
            final Handler handler = new Handler(Looper.getMainLooper());
            handler.post(new Runnable() {
                @Override
                public void run() {
        Toast.makeText(Shell.sInstance, "TXT saved successfully", Toast.LENGTH_SHORT).show();
        }
        });
    } catch (IOException e) {
        e.printStackTrace();
        final IOException e2 = e;
        final Handler handler = new Handler(Looper.getMainLooper());
            handler.post(new Runnable() {
                @Override
                public void run() {
        Toast.makeText(Shell.sInstance, "Error saving TXT: " + e2.getMessage(), Toast.LENGTH_SHORT).show();
        }
        });
    } finally {
        currentFileUri = uri;
        roomindex = new int[21];
        loadTxtToRealmap(uri);
    }
    } else {
    try (OutputStream os = Shell.sInstance.getContentResolver().openOutputStream(uri);
         DataOutputStream dos = new DataOutputStream(os)) {
        dos.writeInt(rr.length);  // número de salas

        for (int size : roomsize2) {
            dos.writeInt(size);  // grava roomsize de cada sala
        }

        dos.write(realmap2);  // grava dados do mapa
        for (int i = 0; i < rr.length; i++) {
        dos.writeInt(life[i] != 0 ? life[i] : 2);
        dos.writeUTF(BlockPics[i] != null && !BlockPics[i].isEmpty() ? BlockPics[i] : "");
        dos.writeUTF(EdgePics[i] != null && !EdgePics[i].isEmpty() ? EdgePics[i] : "");
        dos.writeUTF(ExtrasPic[i] != null && !ExtrasPic[i].isEmpty() ? ExtrasPic[i] : "");
        dos.writeUTF(StripPics[i] != null && !StripPics[i].isEmpty() ? StripPics[i] : "");
        dos.writeUTF(TilesPics[i] != null && !TilesPics[i].isEmpty() ? TilesPics[i] : "");
        dos.writeUTF(stagename[i] != null && !stagename[i].isEmpty() ? stagename[i] : "");
        dos.writeInt(actNumber[i]);
        dos.writeUTF(custommusic && currentmusic[i] != null && !currentmusic[i].isEmpty() ? currentmusic[i] : "");
        }

        final Handler handler = new Handler(Looper.getMainLooper());
            handler.post(new Runnable() {
                @Override
                public void run() {
        Toast.makeText(Shell.sInstance, "File saved successfully", Toast.LENGTH_SHORT).show();
        }
        });
        
        currentFileUri = uri;
        loadMapFile2(uri);
    } catch (IOException e) {
        e.printStackTrace();
        final IOException e2 = e;
        final Handler handler = new Handler(Looper.getMainLooper());
            handler.post(new Runnable() {
                @Override
                public void run() {
        Toast.makeText(Shell.sInstance, "Error saving file: " + e2.getMessage(), Toast.LENGTH_SHORT).show();
        }
        });
    }
    }
}

public static class PlayerService extends Service {
    public MediaPlayer mediaPlayer;
    public static boolean menu;
    public static boolean loop;
    public String name;
    public int count = 1;

    @Override
    public void onCreate() {
        super.onCreate();
        mediaPlayer = new MediaPlayer();
        // Configure o MediaPlayer aqui
    }

    @Override
public int onStartCommand(Intent intent, int flags, int startId) {
    if (buildNotification() != null) {
    startForeground(1, buildNotification());

    if (intent != null) {
        String action = intent.getAction();

        if ("ACTION_PLAY".equals(action)) {
            menu = false;
            loop = true;
            String url = intent.getStringExtra("url");
            if (url == null || url.equals("")) {
            playFromAssets(intent.getStringExtra("asset"));
            return START_STICKY;
            }
            playStream(url);
        } else if ("ACTION_PAUSE".equals(action)) {
            togglePause();
        } else if ("ACTION_RESTART".equals(action)) {
            restartPlayer();
        } else if ("ACTION_RESET".equals(action)) {
            resetPlayer();
        } else if ("ACTION_PLAY_AND_RESET".equals(action)) {
        menu = true;
        loop = false;
        if (mediaPlayer.isPlaying()) {
        mediaPlayer.stop();
        mediaPlayer.reset();
        return START_STICKY;
    } else {
            String url = intent.getStringExtra("url");
            if (url == null || url.equals("")) {
            String name = intent.getStringExtra("asset");
            playFromAssets(name);
            return START_STICKY;
            }
            playStream(url);
    }
        }
    }
    }
    return START_STICKY;
}

public void playFromAssets(String assetName) {
    try {
        if (assetName.equals("menu4.mp3")) assetName = "menu.mp3";
        AssetFileDescriptor afd = getAssets().openFd(assetName);
        mediaPlayer.reset();
        mediaPlayer.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
        afd.close();
        mediaPlayer.prepare();
        if (loop) {
        mediaPlayer.setLooping(true);
        } else {
        mediaPlayer.setLooping(false);
        }
        mediaPlayer.start();
        sendPlayerState();
        
        name = assetName;
        
            mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
    @Override
    public void onCompletion(MediaPlayer mp) {
            if (menu) {
            count++;
            if (count >= 4) count = 1;
            playFromAssets("menu" + (count > 1 ? count : "") + ".mp3");
            }
    }
});
    } catch (IOException e) {
        e.printStackTrace();
    }
}

public void playStream(String url) {
    try {
        mediaPlayer.reset();

        if (url.endsWith(".zip")) {
            final File cache = Shell.sInstance.getCacheDir();
            final File tempFile = new File(cache, "temp.zip");

            downloadZip(url, tempFile, new DownloadCallback() {
                @Override
                public void onSuccess() {
                    try {
                        File mp3File = extractAudioFromZip(tempFile, cache);
                        if (mp3File == null) {
                            throw new IOException("No MP3 files found in ZIP");
                        }
                        startMediaPlayer(mp3File.getAbsolutePath());
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }

                @Override
                public void onError(Exception e) {
                    e.printStackTrace();
                }
            });
        } else {
            startMediaPlayer(url);
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

private void startMediaPlayer(String str) throws IOException {
    mediaPlayer.setDataSource(str);
    mediaPlayer.setLooping(loop);
    mediaPlayer.prepare();
    mediaPlayer.start();
    sendPlayerState();
}

public interface DownloadCallback {
    void onSuccess();
    void onError(Exception e);
}

public void togglePause() {
    if (mediaPlayer != null) {
        if (mediaPlayer.isPlaying()) {
            mediaPlayer.pause();
        } else {
            mediaPlayer.start();
        }
        sendPlayerState();
    }
}

private void sendPlayerState() {
    Intent intent = new Intent("PLAYER_STATE_CHANGED");
    intent.putExtra("isPlaying", mediaPlayer != null && mediaPlayer.isPlaying());
    sendBroadcast(intent);
}

public void restartPlayer() {
    if (mediaPlayer != null) {
        try {
            mediaPlayer.stop();
            mediaPlayer.prepare();
            mediaPlayer.start();
            sendPlayerState();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

public void resetPlayer() {
    if (mediaPlayer != null) {
        mediaPlayer.stop();
        mediaPlayer.reset();
    }
}

    public Notification buildNotification() {
    String CHANNEL_ID = "player_channel";

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        NotificationChannel channel = new NotificationChannel(
            CHANNEL_ID,
            "Player Service",
            NotificationManager.IMPORTANCE_LOW
        );
        NotificationManager manager = getSystemService(NotificationManager.class);
        manager.createNotificationChannel(channel);
    }

    if (Shell.sInstance != null) {
    Notification.Builder builder;

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        builder = new Notification.Builder(Shell.sInstance, CHANNEL_ID);
    } else {
        builder = new Notification.Builder(Shell.sInstance);
    }

    builder
        .setContentTitle("Playing music")
        .setContentText("The editor is playing some music")
        .setSmallIcon(android.R.drawable.ic_media_play)
        .setOngoing(true);

    return builder.build();
    }
    return null;
}

    @Override
    public void onDestroy() {
        super.onDestroy();
        mediaPlayer.stop();
        mediaPlayer.release();
    }

    @Override
    public IBinder onBind(Intent intent) {
        return null; // Não usado
    }

    public void downloadZip(final String urlString, final File outputFile, final DownloadCallback callback) throws IOException {
        new Thread(new Runnable() {
        @Override
        public void run() {
        try {
        URL url = new URL(urlString);
        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
        connection.connect();
    
        InputStream input = connection.getInputStream();
        FileOutputStream output = new FileOutputStream(outputFile);
    
        byte[] buffer = new byte[4096];
        int bytesRead;
        while ((bytesRead = input.read(buffer)) != -1) {
            output.write(buffer, 0, bytesRead);
        }
        output.close();
        input.close();
        connection.disconnect();
        callback.onSuccess();
        } catch (Exception e) {
        callback.onError(e);
        }
        }
        }).start();
    }
    
    public File extractAudioFromZip(File zipFile, File outputDir) throws IOException {
    try (ZipInputStream zis = new ZipInputStream(new FileInputStream(zipFile))) {
        ZipEntry ze;
        while ((ze = zis.getNextEntry()) != null) {
            if (!ze.isDirectory() && ze.getName().toLowerCase().endsWith(".mp3")) {
                File outFile = new File(outputDir, new File(ze.getName()).getName());
                try (FileOutputStream fos = new FileOutputStream(outFile)) {
                    byte[] buffer = new byte[4096];
                    int count;
                    while ((count = zis.read(buffer)) != -1) {
                        fos.write(buffer, 0, count);
                    }
                }
                return outFile;
            }
        }
    }
    return null;
}
}

public class FileImageAdapter extends BaseAdapter {
    public Context context;
    public List<String> files;

    FileImageAdapter(Context context, List<String> files) {
        this.context = context;
        this.files = files;
    }

    @Override
    public int getCount() { return files.size(); }

    @Override
    public Object getItem(int position) { return files.get(position); }

    @Override
    public long getItemId(int position) { return position; }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        if (convertView == null) {
            LinearLayout layout = new LinearLayout(context);
            layout.setOrientation(LinearLayout.HORIZONTAL);

            ImageView imageView = new ImageView(context);
            imageView.setLayoutParams(new LinearLayout.LayoutParams(100, 100));
            layout.addView(imageView);

            TextView textView = new TextView(context);
            layout.addView(textView);

            convertView = layout;
            convertView.setTag(new Holder(imageView, textView));
        }

        Holder holder = (Holder) convertView.getTag();
        String filename = files.get(position);
        holder.textView.setText(filename);

        try {
            InputStream is = context.getAssets().open(filename);
            Bitmap bmp = BitmapFactory.decodeStream(is);
            is.close();
            holder.imageView.setImageBitmap(bmp);
        } catch (IOException e) {
            holder.imageView.setImageResource(android.R.drawable.ic_menu_report_image);
        }

        return convertView;
    }

    class Holder {
        ImageView imageView;
        TextView textView;

        Holder(ImageView iv, TextView tv) {
            imageView = iv;
            textView = tv;
        }
    }
}
}